/*
Pyxis 2.0
Copyright 2010-2011 Thomas W. Holtquist

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

using System;
using System.Collections;
using System.IO;
using System.Reflection;
using System.Runtime;

using Microsoft.SPOT;

using Skewworks.Pyxis.Kernel;

namespace Skewworks.Pyxis.EXT
{
    internal static class AppLoader
    {

        /// <summary>
        /// The interface for a standlone application
        /// </summary>
        interface IApplication
        {
            void RunFromBytes(ref ApplicationKey AppKey, ref PyxisAPI APIRef, ref string path, ref string[] parameters);
            void ServiceFromBytes(ref ServiceKey ServiceKey, ref PyxisAPI APIRef, ref StartMode StartMode, ref string path, ref string[] parameters);
            void StartRegisteredService(PyxisService Service, PyxisAPI APIRef, string[] parameters);
            int GetPXEType(PyxisAPI APIRef, string path);
            AppIcon GetAppIcon(byte[] bytes);
        }

        /// <summary>
        /// A class that can be loaded across applicaiton domains which
        /// implements the IApplicaiton interface
        /// </summary>
        public class Application : MarshalByRefObject, IApplication
        {

            /// <summary>
            /// we need a defautl consturctor to create an instance of this object
            /// across an application domain
            /// </summary>
            public Application()
            {
            }

            public AppIcon GetAppIcon(byte[] bytes)
            {
                // Attempt to execute DLL
                try
                {
                    Assembly asm;
                    MethodInfo[] m;

                    asm = Assembly.Load(bytes);
                    if (asm == null)
                    {
                        return new AppIcon();
                    }

                    Type[] t = asm.GetTypes();
                    for (var j = 0; j < t.Length; j++)
                    {
                        m = t[j].GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance);
                        for (int i = 0; i < m.Length; i++)
                        {
                            if (m[i].Name == "PyxisIcon")
                                return (AppIcon)m[i].Invoke(asm, null);
                        }
                    }
                }
                catch (Exception)
                {
                    // Do nothing
                }

                return new AppIcon();
            }

            public int GetPXEType(PyxisAPI APIRef, string path)
            {
                Assembly asm;
                MethodInfo[] m;
                Type[] t;
                bool bHasApp = false;
                bool bHasService = false;

                // Load Supporting Files
                string[] supporting = Directory.GetFiles(Path.GetDirectoryName(path));
                for (int i = 0; i < supporting.Length; i++)
                {
                    if (supporting[i] != path && Path.GetExtension(supporting[i]).ToLower() == ".pe")
                    {
                        try
                        {
                            Assembly.Load(File.ReadAllBytes(supporting[i]));
                        }
                        catch (Exception) { }
                    }
                }

                // Load ASM
                asm = Assembly.Load(File.ReadAllBytes(path));
                if (asm == null)
                    return -1;
                t = asm.GetTypes();
                for (var j = 0; j < t.Length; j++)
                {
                    m = t[j].GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance);
                    for (int i = 0; i < m.Length; i++)
                    {
                        if (m[i].Name == "PyxisApp")
                            bHasApp = true;
                        else if (m[i].Name == "PyxisService")
                            bHasService = true;
                    }
                }


                if (bHasApp && bHasService)
                    return 2;
                if (bHasService)
                    return 1;
                if (bHasApp)
                    return 0;

                return -1;

            }

            public void RunFromBytes(ref ApplicationKey AppKey, ref PyxisAPI APIRef, ref string path, ref string[] parameters)
            {
                string[] supporting = Directory.GetFiles(Path.GetDirectoryName(path));

                for (int i = 0; i < supporting.Length; i++)
                {
                    if (supporting[i] != path)
                    {
                        try
                        {
                            Assembly.Load(File.ReadAllBytes(supporting[i]));
                        }
                        catch (Exception)
                        {
                            // move along
                        }
                    }
                }

                RunAppFromBytes(AppKey, APIRef, File.ReadAllBytes(path), parameters);
            }

            public void StartRegisteredService(PyxisService Service, PyxisAPI APIRef, string[] parameters)
            {
                string[] supporting = Directory.GetFiles(Path.GetDirectoryName(Service.Key.ServicePath));

                for (int i = 0; i < supporting.Length; i++)
                {
                    if (supporting[i] != Service.Key.ServicePath)
                    {
                        try
                        {
                            Assembly.Load(File.ReadAllBytes(supporting[i]));
                        }
                        catch (Exception)
                        {
                            // move along
                        }
                    }
                }

                // Attempt to execute DLL
                Assembly asm;
                MethodInfo[] m;

                asm = Assembly.Load(File.ReadAllBytes(Service.Key.ServicePath));
                if (asm == null)
                {
                    return;
                }

                Type[] t = asm.GetTypes();

                // Start the Service
                for (var j = 0; j < t.Length; j++)
                {
                    m = t[j].GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance);
                    for (int i = 0; i < m.Length; i++)
                    {
                        if (m[i].Name == "PyxisService")
                        {
                            m[i].Invoke(asm, new object[] { Service.Key, APIRef, parameters });
                            return;
                        }
                    }
                }

            }

            public void ServiceFromBytes(ref ServiceKey ServiceKey, ref PyxisAPI APIRef, ref StartMode StartMode, ref string path, ref string[] parameters)
            {
                string[] supporting = Directory.GetFiles(Path.GetDirectoryName(path));

                for (int i = 0; i < supporting.Length; i++)
                {
                    if (supporting[i] != path)
                    {
                        try
                        {
                            Assembly.Load(File.ReadAllBytes(supporting[i]));
                        }
                        catch (Exception)
                        {
                            // move along
                        }
                    }
                }

                RunServiceFromBytes(ServiceKey, APIRef, StartMode, File.ReadAllBytes(path), parameters);
            }
            
            private void RunAppFromBytes(ApplicationKey AppKey, PyxisAPI APIRef, byte[] bytes, string[] parameters)
            {

                // Attempt to execute DLL
                Assembly asm;
                MethodInfo[] m;

                asm = Assembly.Load(bytes);
                if (asm == null)
                {
                    return;
                }

                Type[] t = asm.GetTypes();
                for (var j = 0; j < t.Length; j++)
                {
                    m = t[j].GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance);
                    for (int i = 0; i < m.Length; i++)
                    {
                        if (m[i].Name == "PyxisApp")
                        {
                            AppStartup myStartup = (AppStartup)m[i].Invoke(asm, new object[] { AppKey, APIRef, parameters });
                            APIRef.ActivateApplication(myStartup, AppKey);
                            return;
                        }
                    }
                }


            }

            private void RunServiceFromBytes(ServiceKey ServiceKey, PyxisAPI APIRef, StartMode StartMode, byte[] bytes, string[] parameters)
            {

                // Attempt to execute DLL
                Assembly asm;
                MethodInfo[] m;
                ServiceInfo SI = new ServiceInfo(ServiceKey.ServicePath, StartMode);

                asm = Assembly.Load(bytes);
                if (asm == null)
                {
                    return;
                }

                Type[] t = asm.GetTypes();

                // Get the Service Info
                for (var j = 0; j < t.Length; j++)
                {
                    m = t[j].GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance);
                    for (int i = 0; i < m.Length; i++)
                    {
                        switch (m[i].Name)
                        {
                            case "PyxisApp":
                                SI.HasGUI = true;
                                break;
                            case "ServiceName":
                                SI.Name = (string)m[i].Invoke(asm, null);
                                break;
                        }
                    }
                }

                // Start the Service
                if (StartMode == Pyxis.StartMode.Automatic)
                {
                    for (var j = 0; j < t.Length; j++)
                    {
                        m = t[j].GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance);
                        for (int i = 0; i < m.Length; i++)
                        {
                            if (m[i].Name == "PyxisService")
                            {
                                m[i].Invoke(asm, new object[] { ServiceKey, APIRef, parameters });
                                SI.IsRunning = true;
                                APIRef.RegisterService(ServiceKey, SI);
                                return;
                            }
                        }
                    }
                }
                else
                    APIRef.RegisterService(ServiceKey, SI);


            }

        }

        #region Variables

        private static IApplication iApp = null;

        #endregion

        /// <summary>
        /// The executable entry point.
        /// </summary>
        public static bool LaunchApplication(PyxisAPI APIRef, string path, string[] parameters)
        {
            int AppID = GetAppID(APIRef);

            // Create the Application
            PyxisApplication PA = new PyxisApplication();
            PA.Forms = new ArrayList();
            PA.Domain = AppDomain.CreateDomain("PyxisApp" + AppID);
            PA.Key = new ApplicationKey(AppID, PA.Domain, FileManager.NormalizeDirectory(Path.GetDirectoryName(path)));
            APIRef._runningApps.Add(PA);

            // create an instance of the IApplication type
            // please note that the following call will also load the assembly that contains that type
            // which we pass as first parameter 
            try
            {
                iApp = (IApplication)PA.Domain.CreateInstanceAndUnwrap(typeof(IApplication).Assembly.FullName, typeof(Application).FullName);

                // Allow system time to catch up
                System.Threading.Thread.Sleep(100);

                // Launch the Program
                iApp.RunFromBytes(ref PA.Key, ref APIRef, ref path, ref parameters);

                return true;
            }
            catch (Exception e)
            {
                APIRef.Prompt("Failed to launch application!\n" + e.Message, "System Alert", PromptType.OKOnly);
                APIRef._runningApps.Remove(PA);
                return false;
            }
        }

        public static bool RegisterService(PyxisAPI APIRef, StartMode StartMode, string path, string[] parameters)
        {
            int AppID = -1;
            ServiceKey SK;

            AppID = GetAppID(APIRef);

            // Create the Service Key
            SK = new ServiceKey(AppID, AppDomain.CreateDomain("PyxisService" + AppID), path);

            // create an instance of the IApplication type
            // please note that the following call will also load the assembly that contains that type
            // which we pass as first parameter 
            try
            {
                iApp = (IApplication)SK.AppDomain.CreateInstanceAndUnwrap(typeof(IApplication).Assembly.FullName, typeof(Application).FullName);

                // Allow system time to catch up
                System.Threading.Thread.Sleep(100);

                // Launch the Program
                iApp.ServiceFromBytes(ref SK, ref APIRef, ref StartMode, ref path, ref parameters);

                return true;
            }
            catch (Exception e)
            {
                APIRef.Prompt("Failed to launch service!\n" + e.Message, "System Alert", PromptType.OKOnly);
                return false;
            }
            
        }

        public static PyxisService StartRegisteredService(PyxisAPI APIRef, PyxisService Service, string[] Parameters)
        {
            int AppID = -1;
            ServiceKey SK;
            PyxisService PS = new PyxisService();

            AppID = GetAppID(APIRef);

            // Create the Service Key
            PS.Key = new ServiceKey(AppID, AppDomain.CreateDomain("PyxisService" + AppID), Service.Key.ServicePath);
            PS.Info = Service.Info;

            // create an instance of the IApplication type
            // please note that the following call will also load the assembly that contains that type
            // which we pass as first parameter 
            try
            {
                iApp = (IApplication)PS.Key.AppDomain.CreateInstanceAndUnwrap(typeof(IApplication).Assembly.FullName, typeof(Application).FullName);

                // Allow system time to catch up
                System.Threading.Thread.Sleep(100);

                // Launch the Program
                iApp.StartRegisteredService(Service, APIRef, Parameters);

                PS.Info.IsRunning = true;
                return PS;
            }
            catch (Exception e)
            {
                APIRef.Prompt("Failed to launch service!\n" + e.Message, "System Alert", PromptType.OKOnly);
                return Service;
            }
        }

        public static AppIcon GetApplicationIcon(byte[] bytes)
        {
            AppIcon AI;

            // Create the Domain
            AppDomain ad = AppDomain.CreateDomain("PyxisAppDesktopIcon");

            // create an instance of the IApplication type
            // please note that the following call will also load the assembly that contains that type
            // which we pass as first parameter 
            if (iApp == null)
            {
                try
                {
                    iApp = (IApplication)ad.CreateInstanceAndUnwrap(typeof(IApplication).Assembly.FullName, typeof(Application).FullName);
                }
                catch (Exception)
                {
                    AppDomain.Unload(ad);
                    return new AppIcon();
                }
            }

            // Get the Icon
            AI = iApp.GetAppIcon(bytes);

            // Destroy Domain
            iApp = null;
            AppDomain.Unload(ad);

            return AI;
        }

        private static int GetAppID(PyxisAPI APIRef, string Prefix = "PyxisApp")
        {
            bool bConflict = false;
            int AppID = 0;
            int i;
            PyxisApplication PA;

            while (true)
            {
                bConflict = false;
                for (i = 0; i < APIRef._runningApps.Count; i++)
                {
                    PA = (PyxisApplication)APIRef._runningApps[i];
                    if (PA.Domain.FriendlyName == Prefix + AppID)
                    {
                        bConflict = true;
                        break;
                    }
                }
                if (!bConflict) return AppID;
                AppID++;
            }
        }

        public static int GetPXEType(PyxisAPI APIRef, string path)
        {
            int AppID = GetAppID(APIRef, "PASC");

            // Create the Application
            PyxisApplication PA = new PyxisApplication();
            PA.Forms = new ArrayList();
            PA.Domain = AppDomain.CreateDomain("PASC" + AppID);
            PA.Key = new ApplicationKey(AppID, PA.Domain, FileManager.NormalizeDirectory(Path.GetDirectoryName(path)));

            // create an instance of the IApplication type
            // please note that the following call will also load the assembly that contains that type
            // which we pass as first parameter 
            try
            {
                iApp = (IApplication)PA.Domain.CreateInstanceAndUnwrap(typeof(IApplication).Assembly.FullName, typeof(Application).FullName);

                // Allow system time to catch up
                System.Threading.Thread.Sleep(100);

                int i = iApp.GetPXEType(APIRef, path);

                try
                {
                    AppDomain.Unload(PA.Domain);
                }
                catch (Exception) { }

                return i;

            }
            catch (Exception e)
            {
                APIRef.Prompt("Failed to open PXE!\n" + e.Message, "System Alert", PromptType.OKOnly);
                try
                {
                    AppDomain.Unload(PA.Domain);
                }
                catch (Exception) { }
                return -1;
            }
        }

    }
}